/****************************************************************
 *
 *        Copyright 2013, Big Switch Networks, Inc.
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *        http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the
 * License.
 *
 ***************************************************************/

#include <PPE/ppe_config.h>
#include <PPE/ppe.h>
#include "ppe_log.h"
#include "ppe_util.h"

#include <IOF/iof.h>

/**
 * Field Information Table
 */
ppe_field_info_t ppe_field_info_table[] =
    {
/**
 * This table is autogenerated from the PPE field xmacro entries.
 *
 */
#define PPE_FIELD_ENTRY(_header, _field, _size_bits, _offset_bytes, _shift_bits) \
        { PPE_FIELD_##_header##_##_field, PPE_HEADER_##_header,         \
          _size_bits, _offset_bytes, _shift_bits                        \
        },
#include <PPE/ppe.x>
        { -1, -1, 0, 0, 0 }
    };


/**
 * Determine the number of bytes required to store
 * the given field.
 */
#define FIELD_SIZE_BYTES(_fi)                           \
    ( (fi->size_bits / 8) + ((fi->size_bits % 8) ? 1 : 0) )


int
ppe_field_get(ppe_packet_t* ppep, ppe_field_t field, uint32_t* rv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_field_get_header(ppep->headers[fi->header].start,
                                field, rv);
}

int
ppe_field_get_header(uint8_t* p, ppe_field_t field, uint32_t* rv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_field_info_get_header(p, fi, rv);
}

int
ppe_field_info_get_header(uint8_t* p, ppe_field_info_t* fi, uint32_t* rv)
{
    uint32_t v = 0;

    if(p) {
        /* Advance to the field location */
        p += fi->offset_bytes;

        /* Retrieve and convert the field data */
        switch(FIELD_SIZE_BYTES(fi))
            {
            case 1:
                {
                    v = p[0];
                    break;
                }
            case 2:
                {
                    v = p[0] << 8 | p[1];
                    break;
                }
            case 3:
                {
                    v = p[0] << 16 | p[1] << 8 | p[2];
                    break;
                }
            case 4:
                {
                    v = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
                    break;
                }
            default:
                {
                    /* Error */
                    return -1;
                }
            }

        /* Shift and mask, if necessary */
        v >>= fi->shift_bits;
        if(fi->size_bits < 32) {
            v &= ( (1 << fi->size_bits) -1 );
        }
        *rv = v;
        return 0;
    }
    else {
        /* The field does not exist in the packet. */
        return -1;
    }
}


int
ppe_field_set(ppe_packet_t* ppep, ppe_field_t field,
              uint32_t sv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_field_set_header(ppep->headers[fi->header].start,
                                field, sv);
}

int
ppe_field_set_header(uint8_t* p, ppe_field_t field, uint32_t sv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_field_info_set_header(p, fi, sv);
}

int
ppe_field_info_set_header(uint8_t* p, const ppe_field_info_t* fi, uint32_t sv)
{
    uint32_t andmask;

    if(p) {
        /* Advance to the field location */
        p += fi->offset_bytes;

        /* Mask value to appropriate size */
        if(fi->size_bits < 32) {
            sv &= ( (1 << fi->size_bits)-1 );
        }
        /* Shift value into appropriate location */
        sv <<= fi->shift_bits;
        /* Generate mask for appropriate location */
        if(fi->size_bits < 32) {
            andmask = ~( ((1 << fi->size_bits) - 1) << fi->shift_bits);
        }
        else {
            andmask = 0;
        }
        switch(FIELD_SIZE_BYTES(fi))
            {
            case 1:
                {
                    p[0] &= andmask;
                    p[0] |= sv;
                    break;
                }
            case 2:
                {
                    p[0] &= (andmask >> 8);
                    p[0] |= (sv >> 8);
                    p[1] &= (andmask);
                    p[1] |= (sv);
                    break;
                }
            case 3:
                {
                    p[0] &= (andmask >> 16);
                    p[0] |= (sv >> 16);
                    p[1] &= (andmask >> 8);
                    p[1] |= (sv >> 8);
                    p[2] &= (andmask);
                    p[2] |= (sv);
                    break;
                }
            case 4:
                {
                    p[0] &= (andmask >> 24);
                    p[0] |= (sv >> 24);
                    p[1] &= (andmask >> 16);
                    p[1] |= (sv >> 16);
                    p[2] &= (andmask >> 8);
                    p[2] |= (sv >> 8);
                    p[3] &= (andmask);
                    p[3] |= (sv);
                    break;
                }
            default:
                /* Error */
                return -1;
            }
        return 0;
    }
    else {
        /* The field does not exist in the packet. */
        return -1;
    }
}

int
ppe_wide_field_get(ppe_packet_t* ppep, ppe_field_t field,
                       uint8_t* rv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_wide_field_get_header(ppep->headers[fi->header].start,
                                     field, rv);
}


int
ppe_wide_field_get_header(uint8_t* p, ppe_field_t field,
                          uint8_t* rv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_wide_field_info_get_header(p, fi, rv);
}

int
ppe_wide_field_info_get_header(uint8_t* p, ppe_field_info_t* fi,
                               uint8_t* rv)
{
    if(p) {
        p += fi->offset_bytes;
        PPE_MEMCPY(rv, p, fi->size_bits/8);
        return fi->size_bits/8;
    }
    else {
        /* field does not exist */
        return -1;
    }
}

int
ppe_wide_field_set(ppe_packet_t* ppep, ppe_field_t field,
                   uint8_t* sv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_wide_field_set_header(ppep->headers[fi->header].start,
                                     field, sv);
}

int
ppe_wide_field_set_header(uint8_t* p, ppe_field_t field,
                          uint8_t* sv)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_wide_field_info_set_header(p, fi, sv);
}

int
ppe_wide_field_info_set_header(uint8_t* p, const ppe_field_info_t* fi,
                               uint8_t* sv)
{
    if(p) {
        p += fi->offset_bytes;
        if(sv) {
            PPE_MEMCPY(p, sv, fi->size_bits/8);
        }
        else {
            PPE_MEMSET(p, 0, fi->size_bits/8);
        }
        return 0;
    }
    else {
        /* field does not exist */
        return -1;
    }
}

int
ppe_field_exists(ppe_packet_t* ppep, ppe_field_t field)
{
    return ppep->headers[ppe_field_info_table[field].header].start
        != NULL;
}

uint8_t*
ppe_fieldp_get(ppe_packet_t* ppep, ppe_field_t field)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return ppe_fieldp_get_header(ppep->headers[fi->header].start, field);
}
uint8_t*
ppe_fieldp_get_header(uint8_t* p, ppe_field_t field)
{
    ppe_field_info_t* fi = ppe_field_info_table + field;
    return (p) ? (p + fi->offset_bytes) : NULL;
}

const ppe_field_info_t*
ppe_field_info_get(ppe_field_t field)
{
    return ppe_field_info_table+field;
}


int
ppe_field_copy(ppe_packet_t* ppep, ppe_field_t dst_field,
               ppe_field_t src_field)
{
    if(ppe_field_exists(ppep, src_field) &&
       ppe_field_exists(ppep, dst_field)) {
        uint32_t v = 0;
        ppe_field_get(ppep, src_field, &v);
        ppe_field_set(ppep, dst_field, v);
        return 0;
    }
    return -1;
}

int
ppe_wide_field_copy(ppe_packet_t* ppep, ppe_field_t dst_field,
                    ppe_field_t src_field)
{
    if(ppe_field_exists(ppep, src_field) &&
       ppe_field_exists(ppep, dst_field)) {
        ppe_wide_field_set(ppep, dst_field,
                           ppe_fieldp_get(ppep, src_field));
        return 0;
    }
    return -1;
}

/**************************************************************************//**
 *
 * Dynamic Key Management
 *
 *
 *****************************************************************************/
#define DFK_FIELD_SIZE(_sbits) ( (_sbits/8) + ((_sbits % 8) ? 1 : 0) )

int
ppe_dfk_init(ppe_dfk_t* dfk, ppe_field_t* fields, int fcount)
{
    int i;
    int size = 0; /* offset_bytes */
    ppe_field_info_t* fi;
    ppe_field_info_t* efi;

    PPE_MEMSET(dfk, 0, sizeof(*dfk));
    dfk->header.efis = aim_zmalloc(fcount*sizeof(ppe_field_info_t));
    dfk->header.fcount = fcount;

    for(i = 0; i < fcount; i++) {
        fi = ppe_field_info_table + fields[i];
        efi = &dfk->header.efis[i];

        PPE_MEMCPY(efi, fi, sizeof(*efi));
        efi->offset_bytes = size;
        size += DFK_FIELD_SIZE(fi->size_bits);
    }

    dfk->data = aim_zmalloc(size);
    dfk->size = size;
    return dfk->size;
}

int
ppe_dfk_destroy(ppe_dfk_t* dfk)
{
    if(dfk) {
        if(dfk->header.efis) {
            aim_free(dfk->header.efis);
        }
        if(dfk->data) {
            aim_free(dfk->data);
        }
    }
    return 0;
}

int
ppe_dfk_show(ppe_dfk_t* dfk, aim_pvs_t* pvs)
{
    int i;
    int rv;
    iof_t iof;
    iof_init(&iof, pvs);

    if(dfk->mask == 0) {
        iof_iprintf(&iof, "keymask = 0");
        return 0;
    }
    iof_indent(&iof);
    for(i = 0; i < dfk->header.fcount; i++) {
        ppe_field_info_t* fi = &dfk->header.efis[i];
        if(dfk->mask & (1ull<<i)) {
            if(fi->size_bits <= 32) {
                uint32_t data;
                ppe_field_info_get_header(dfk->data, fi, &data);
                iof_uprintf(&iof, "%s=0x%x (%d) ",
                            ppe_field_name(fi->field), data, data);
            }
            else {
                /* fixme */
                unsigned int i;
                uint8_t data[256];
                rv = ppe_wide_field_info_get_header(dfk->data, fi, data);
                AIM_ASSERT(rv >= 0, "Failed to get field after checking for existence");
                iof_uprintf(&iof, "%s={ ", ppe_field_name(fi->field));
                for(i = 0; i < fi->size_bits / 8; i++) {
                    iof_uprintf(&iof, "%.2x", data[i]);
                }
                iof_uprintf(&iof, " } ");
            }
        }
    }
    iof_uprintf(&iof, "\n");
    return 0;
}

int
ppe_dfk_field_set(ppe_dfk_t* dfk, ppe_field_t field, uint32_t data)
{
    int i;
    for(i = 0; i < dfk->header.fcount; i++) {
        ppe_field_info_t* fi = &dfk->header.efis[i];
        if(field == fi->field) {
            ppe_field_info_set_header(dfk->data, fi, data);
            dfk->mask |= (1ull<<i);
            return 0;
        }
    }
    AIM_LOG_TRACE("field %{ppe_field} is not in the fields array.", field);
    return -1;
}

int
ppe_dfk_field_get(ppe_dfk_t* dfk, ppe_field_t field, uint32_t* value)
{
    int i;
    for(i = 0; i < dfk->header.fcount; i++) {
        ppe_field_info_t* fi = &dfk->header.efis[i];
        if(field == fi->field) {
            if(dfk->mask & (1ull<<i)) {
                ppe_field_info_get_header(dfk->data, fi, value);
                return 0;
            }
            else {
                /* Field is not set */
                return -1;
            }
        }
    }
    AIM_LOG_TRACE("field %{ppe_field} is not in the fields array.", field);
    return -1;
}

int
ppe_dfk_wide_field_set(ppe_dfk_t* dfk, ppe_field_t field, uint8_t* data)
{
    int i;
    for(i = 0; i < dfk->header.fcount; i++) {
        ppe_field_info_t* fi = &dfk->header.efis[i];
        if(field == fi->field) {
            ppe_wide_field_info_set_header(dfk->data, fi, data);
            dfk->mask |= (1ull<<i);
            return 0;
        }
    }
    AIM_LOG_TRACE("field %{ppe_field} is not in the fields array.", field);
    return -1;
}

int
ppe_dfk_wide_field_get(ppe_dfk_t* dfk, ppe_field_t field, uint8_t* data)
{
    int i;
    for(i = 0; i < dfk->header.fcount; i++) {
        ppe_field_info_t* fi = &dfk->header.efis[i];
        if(field == fi->field) {
            if(dfk->mask & (1ull<<i)) {
                ppe_wide_field_info_get_header(dfk->data, fi, data);
                return 0;
            }
            else {
                /* Field is not set */
                return -1;
            }
        }
    }
    AIM_LOG_TRACE("field %{ppe_field} is not in the fields array.", field);
    return -1;
}

int
ppe_packet_dfk(ppe_packet_t* ppep, ppe_dfk_t* dfk)
{
    int i;
    int rv;

    PPE_MEMSET(dfk->data, 0, dfk->size);
    dfk->mask = 0;

    for(i = 0; i < dfk->header.fcount; i++) {
        ppe_field_info_t* fi = &dfk->header.efis[i];

        if(ppe_field_exists(ppep, fi->field)) {
            if(fi->size_bits <= 32) {
                uint32_t data;
                rv = ppe_field_get(ppep, fi->field, &data);
                AIM_ASSERT(rv == 0, "Failed to get field after checking for existence");
                ppe_field_info_set_header(dfk->data, fi, data);
            }
            else {
                uint8_t* datap = ppe_fieldp_get(ppep, fi->field);
                ppe_wide_field_info_set_header(dfk->data, fi, datap);
            }
            dfk->mask |= (1ull<<i);
        }
    }
    return dfk->size;
}
